/*
 * Copyright (C) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Makes a function suitable for use in Array.sort().
 * @param fn A function that, given an array element, produces a value to use in
 *           comparisons.
 * @return A function that compares two array elements.
 */
function makeComparer(fn) {
  return function(a, b) {
    var fa = fn(a);
    var fb = fn(b);
    return fa < fb ? -1 : (fa == fb ? 0 : 1);
  };
}

var ESC = 27;
var START_DATE = new Date("April 13, 2010");
var MILLIS_PER_DAY = 24 * 60 * 60 * 1000;


//
// State manipulation
//

/**
 * Gets the entire state of all release.
 * @return An array of state dictionaries. See getState() below for a
 *         description of the dictionary format. Each index represents
 *         a week since.
 */
function getAllState() {
  var i = 0;
  var result = [];
  var next = getState(i);
  while (next.name != null && next.coord != null && next.status != null) {
    result.push(next);
    i += 1;
    next = getState(i);
  }
  return result;
}

/**
 * Gets the state of one release.
 * @param index The week to get.
 * @return A dictionary: {
 *            status: string,
 *            coord: string, participantId of coordinator
 *            name: string name of release}
 */
function getState(index) {
  var gadgetState = wave.getState();
  return {
    name: gadgetState.get(index + '.name', null),
    coord: gadgetState.get(index + '.coord', null),
    status: gadgetState.get(index + '.status', null)}
}


/**
 * Writes the state.
 * @param index The week to write.
 * @param field The name of the field to write.
 * @param value The value to write.
 */
function putState(index, field, value) {
  var delta = {};
  delta[index + '.' + field] = value;
  wave.getState().submitDelta(delta);
}

//
// UI manipulation
//

/**
 * Sets a JQuery data item on the given element's row.
 * @param element An element that is a row, or a child of a row.
 * @param index The data to set.
 */
function setIndex(element, index) {
  element.closest('li.row').data('index', index);
}

/**
 * Gets the jquery data item on the given elements row.
 * @param element An element that is a row, or a child of a row.
 * @return The data previously set with setIndex().
 */
function getIndex(element) {
  return element.closest('li.row').data('index');
}

/**
 * Gets the actual date corresponding to the week index.
 * @param index The week index.
 * @return A Date.
 */
function getDateForIndex(index) {
  return new Date(START_DATE.getTime() + 7 * MILLIS_PER_DAY * index);
}

/**
 * Formats a date for display.
 * @param dt a Date
 * @return a String
 */
function formatDate(dt) {
  var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
  return dt.getFullYear() + "-" + MONTHS[dt.getMonth()] + "-" + dt.getDate();
}

/**
 * Appends a row to the on screen list.
 */
function addUiRow() {
  var lastRow = $(".stuff .row:last-child");
  var newIndex = $(".stuff .row.val").size();
  var newRow = $("#rowTemplate > .row.val").clone();
  lastRow.after(newRow);
  newRow.find("div.cell.date .content").append(formatDate(getDateForIndex(newIndex)));
  setIndex(newRow, newIndex);
}

/**
 * Updates the coordinator name select.
 * @param select The select element.
 * @param coordId participantId of the coordinator.
 */
function updateCoordinator(select, coordId) {
  function makeOption(participantId, selected) {
    var result = "<option ";
    if (selected) {
      result += "selected=selected ";
    }
    result += "value=\'" + gadgets.util.escapeString(participantId) + "'>";
    if (participantId == "") {
      result += "No one";
    } else {
      var participant = wave.getParticipantById(participantId);
      if (participant == null) {
        result += gadgets.util.escapeString(participantId);
      } else {
        result += gadgets.util.escapeString(participant.getDisplayName());
      }
    }
    result += "</option>";
    return result;
  }

  // Set the currently selected participant
  select.empty();
  select.append(makeOption(coordId, true));

  // Add everyone on the wave, in alphabetical order, except the one already set
  var participants = wave.getParticipants();
  participants.sort(makeComparer(function (p) { return p.getDisplayName(); }));

  for (var i = 0; i < participants.length; i++) {
    var pId = participants[i].getId();
    if (coordId != pId) {
      select.append(makeOption(pId, false));
    }
  }
}

/**
 * Updates a row on the UI with a given state.
 * @param index Week index.
 * @param state The state to display for that week.
 */
function updateUiRow(index, state) {
  wave.log("updateUiRow(" + index + ", " + state + ")");
  var existingRows = ".stuff li.row.val";

  // Ensure row exists
  if ($(existingRows).size() == 0) {
    addUiRow();
  }
  while ($(existingRows).size() <= index) {
    addUiRow();
  }

  // Set its values
  var newRow = $(existingRows).eq(index);
  newRow.find("div.cell.name input").val(gadgets.util.escapeString(state.name));
  updateCoordinator(newRow.find("div.cell.coord select"), state.coord);
  newRow.find("div.cell.status input").val(gadgets.util.escapeString(state.status));

  // Set coordinator picture, if available
  var participant = wave.getParticipantById(state.coord);
  if (participant != null) {
    var img = newRow.find("div.cell.coord .thumbnail img");
    img.attr("src", participant.getThumbnailUrl());
    img.removeClass("noshow");
  }
}


//
// Event handling
//

/**
 * Handles wave having been updated - either participants or wave.
 */
function onWaveUpdated() {
  // TODO(alan): separate handling of participant and wave state updates.
  if (wave.getState() == null || wave.getParticipants().length == 0) {
    // Get out if gadget not yet fully initialised
    wave.log("wave state: " + wave.getState());
    wave.log("participants: " + wave.getParticipants().length);
    return;
  }
  var states = getAllState();
  for (var i = 0; i < states.length; i++) {
    updateUiRow(i, states[i]);
  }
  gadgets.window.adjustHeight();
}


/**
 * Handles key being pressed on an input element.
 * @param event The key press event.
 * @param field name of the state field corresponding to the input element.
 */
function onRowInputKey(event, field) {
  if (event.keyCode == ESC) {
    var element = $(event.target);
    element.val(getState(getIndex(element))[field]);
  }
}


/**
 * Gadget initialisation function.
 */
function init() {
  // Set up input fields to act on focus and blur
  $("input:text").live("focus", function(event) {
    $(this).addClass("focused");
  });
  $("input:text").live("blur", function(event) {
    $(this).removeClass("focused");
  });

  // Capture edits and escape key
  $("div.cell.name input:text").live(
      'blur', function() { putState(getIndex($(this)), 'name', $(this).val()); });
  $("div.cell.name input:text").live(
      'keydown', function(event) { onRowInputKey(event, 'name'); });
  $("div.cell.coord select").live(
      'change', function() {
        putState(getIndex($(this)), 'coord', $(this).children(":selected").first().val());
   })
  $("div.cell.status input:text").live(
      'blur', function() { putState(getIndex($(this)), 'status', $(this).val()); });
  $("div.cell.status input:text").live(
      'keydown', function(event) { onRowInputKey(event, 'status'); });

  // Set up add row button
  $("#addRow").click(function(event) {
    // Add in a blank state
    var newIndex = getAllState().length;
    putState(newIndex, 'name', '');
    putState(newIndex, 'coord', '');
    putState(newIndex, 'status', '');

    // TODO(alan):set focus on new release, disable button until last state is
    // not blank
  });

  wave.setParticipantCallback(onWaveUpdated);
  wave.setStateCallback(onWaveUpdated);
}

gadgets.util.registerOnLoadHandler(init);
